package xgotool

import (
	"io"
	"mime/multipart"
	"os"
	"path"
	"strings"

	"gitee.com/xiaoyutab/xgotool/xerror"
	"gitee.com/xiaoyutab/xgotool/xstring"
)

// 文件上传结构
type FileConfig struct {
	SavePath   string `json:"save_path"`   // 保存目录，若结尾不为 `/` 会自动追加 `/` 进行间隔
	SaveName   string `json:"save_name"`   // 保存文件名若无后缀则自动追加原后缀信息
	UpImage    bool   `json:"up_image"`    // 是否允许上传图片
	UpDocument bool   `json:"up_document"` // 是否允许上传文档
	UpAudio    bool   `json:"up_audio"`    // 是否允许上传音频
	UpVideo    bool   `json:"up_video"`    // 是否允许上传视频
	UpConfig   bool   `json:"up_config"`   // 是否允许上传配置文件
	UpZip      bool   `json:"up_zip"`      // 是否允许上传压缩包文件
	UpOther    bool   `json:"up_other"`    // 是否允许上传其他后缀文件
	UpNone     bool   `json:"up_none"`     // 是否允许无后缀文件上传
	MaxSize    int64  `json:"max_size"`    // 上传文件的最大大小配置，单位：B，0表示不限制
	IsImage    bool   `json:"is_image"`    // 上传文件是图片 // 上传处理过程中会回写的变量
	IsDocument bool   `json:"is_document"` // 上传文件是文档 // 上传处理过程中会回写的变量
	IsAudio    bool   `json:"is_audio"`    // 上传文件是音频 // 上传处理过程中会回写的变量
	IsVideo    bool   `json:"is_video"`    // 上传文件是视频 // 上传处理过程中会回写的变量
	IsConfig   bool   `json:"is_config"`   // 上传文件是配置文件 // 上传处理过程中会回写的变量
	IsZip      bool   `json:"is_zip"`      // 上传文件是压缩包文件 // 上传处理过程中会回写的变量
	IsOther    bool   `json:"is_other"`    // 上传文件是其他后缀文件 // 上传处理过程中会回写的变量
	MD5        string `json:"md5"`         // 文件的MD5值 // 上传处理过程中会回写的变量
	SHA1       string `json:"sha1"`        // 文件的SHA1的值 // 上传过程中会回写的变量
	Size       int64  `json:"size"`        // 文件的大小 // 上传处理过程中会回写的变量
}

// 上传文件处理
// 此方法会根据上传文件的后缀来判断是什么类型的文档，具体判定格式见下方lists
// PS：此方法仅作为上传/保存文件的目录，并不会生成缩略图/低质图等，缩略图等信息请再安排后续操作进行处理
func UploadFile(f *multipart.FileHeader, fc *FileConfig) error {
	ext := strings.ToLower(path.Ext(f.Filename))
	if ext != "" {
		// 移除后缀前的.符号
		ext = ext[1:]
	}
	// 后缀判断
	err := checkExt(ext, fc)
	if err != nil {
		return err
	}
	// 保存文件位置判断
	if err := checkConf(ext, fc); err != nil {
		return err
	}
	// 文件大小限制
	if fc.MaxSize > 0 {
		if f.Size > fc.MaxSize {
			return xerror.New("文件大小超出限制")
		}
	}
	fc.Size = f.Size
	// 进行文件上传
	src, err := f.Open()
	if err != nil {
		return err
	}
	defer src.Close()
	out, err := os.Create(fc.SavePath + fc.SaveName)
	if err != nil {
		return err
	}
	defer out.Close()
	_, err = io.Copy(out, src)
	if err == nil {
		fc.MD5 = MD5File(fc.SavePath + fc.SaveName)
		fc.SHA1 = SHA1File(fc.SavePath + fc.SaveName)
	}
	return err
}

// 检测配置项能否允许上传，如检测大小、上传目录等
//
//	ext	文件后缀
//	fc	配置项信息
func checkConf(ext string, fc *FileConfig) error {
	// 上传目录判断
	if _, err := os.Stat(fc.SavePath); err != nil {
		// 创建目录，因创建目录存在权限问题，所以此处使用mask方法进行创建并授权
		if err = os.MkdirAll(fc.SavePath, os.ModePerm); err != nil {
			return err
		}
	}
	// 保存目录判断
	if fc.SavePath[len(fc.SavePath)-1:] != "/" {
		fc.SavePath += "/"
	}
	if fc.SaveName == "" {
		// 如果savename传入的值为空，则生成一个30位长度的随机数，用来作为文件名进行存储
		fc.SaveName = xstring.Random(30)
	}
	if strings.ToLower(path.Ext(fc.SaveName)) == "" {
		fc.SaveName += "." + ext
	}
	if _, err := os.Stat(fc.SavePath + fc.SaveName); err == nil {
		return xerror.New("上传目录已存在")
	}
	return nil
}

// 判断是否允许某后缀文件上传
//
//	ext	后缀文件名
//	fc	上传配置项
func checkExt(ext string, fc *FileConfig) error {
	// 图片
	img := []string{"png", "jpg", "jpeg", "gif", "webp", "avif", "ico", "cur", "tga", "iff", "ani", "tiff", "bmp", "pcx", "jfif"}
	// 文档
	doc := []string{"txt", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf", "md", "mobi", "epub"}
	// 音频
	aud := []string{"mp3", "aac", "wav", "wma", "cda", "flac", "m4a", "mid", "mka", "mp2", "mpa", "mpc", "ape", "ofr", "ogg", "ra", "wv", "tta", "ac3", "dts"}
	// 视频
	vid := []string{"mpg", "mpeg", "avi", "rm", "rmvb", "mov", "wmv", "asf", "dat", "mp4", "asx", "wvx", "mpe", "mpa"}
	// 配置项
	con := []string{"ini", "yml", "env", "conf", "json"}
	// 压缩包
	zip := []string{"zip", "rar", "7z", "tar", "bz", "gz", "tgz", "tbz", "bz2"}
	// 判断文件后缀是否允许上传
	if ext != "" {
		upd := false
		if !upd && fc.UpImage {
			upd = InArray(ext, img)
			fc.IsImage = upd
		}
		if !upd && fc.UpDocument {
			upd = InArray(ext, doc)
			fc.IsDocument = upd
		}
		if !upd && fc.UpAudio {
			upd = InArray(ext, aud)
			fc.IsAudio = upd
		}
		if !upd && fc.UpVideo {
			upd = InArray(ext, vid)
			fc.IsVideo = upd
		}
		if !upd && fc.UpConfig {
			upd = InArray(ext, con)
			fc.IsConfig = upd
		}
		if !upd && fc.UpZip {
			upd = InArray(ext, zip)
			fc.IsZip = upd
		}
		if fc.UpOther {
			// 是否允许其他格式文件上传
			upd = true
			fc.IsOther = true
		}
		if !upd {
			return xerror.New("文件格式不允许上传")
		}
	} else if !fc.UpNone {
		return xerror.New("禁止无后缀文件上传")
	}
	return nil
}
